package be.groept.ace.cursusplanning.utils;

import java.util.Calendar;
import java.util.Locale;

import be.groept.ace.cursusplanning.persistency.IPersistentieController;
import be.groept.ace.cursusplanning.persistency.IPersistentieProvider;
import be.groept.ace.cursusplanning.persistency.Persisteerbaar;


/**
 * Eigen klasse Datum
 * 
 * @author Kevin Brigitta
 * @author Gouke Leplat
 * @author Abdelkarim Boujida
 * @version %G%, %I%
 */
public class Datum implements Comparable<Datum>, Persisteerbaar{

	private enum JaarLimiet {
		MIN(1900), MAX(2100);
		private int jaartal;

		private JaarLimiet(int jaartal) {
			this.jaartal = jaartal;
		}

		public int getJaartal() {
			return jaartal;
		}
	}

	private Integer dag;
	private Maand maand;
	private Integer jaar;

	private IPersistentieProvider pProvider;
	/**
	 * Maakt een nieuw Datum object aan van de huidige datum
	 * 
	 * @throws IllegalArgumentException
	 */
	public Datum() throws IllegalArgumentException {
		Calendar calendar = Calendar.getInstance();
		setDatum(calendar.get(Calendar.DAY_OF_MONTH), calendar
				.get(Calendar.MONTH), calendar.get(Calendar.YEAR));
	}

	/**
	 * Maakt een nieuw Datum object aan met een datum object
	 * 
	 * @param datum
	 * @throws IllegalArgumentException
	 */
	public Datum(Datum datum) throws IllegalArgumentException {
		if (datum == null) throw new IllegalArgumentException();
		setDatum(datum.getDag(), datum.getMaand().getMaandNummer(), datum
				.getJaar());
	}

	/**
	 * Maakt een nieuw Datum object aan met de opgegeven dag, maand en jaar
	 * 
	 * @param dag
	 *            waarde van de dag als integer
	 * @param maand
	 *            waarde van de maand als integer
	 * @param jaar
	 *            waarde van het jaar als integer
	 * @throws IllegalArgumentException
	 */
	public Datum(Integer dag, Integer maand, Integer jaar) throws IllegalArgumentException {
		setDatum(dag, maand, jaar);
	}

	/**
	 * Maakt een nieuw Datum object aan met datum in string formaat
	 * <p>
	 * formaat van de input string moet DD/MM/JJJJ zijn met een leidende 0
	 * indien nodig. Anders wordt er een exception gegooid.
	 * 
	 * @param datum
	 *            String in formaat DD/MM/JJJJ
	 * @throws IllegalArgumentException
	 */
	public Datum(String datum) throws IllegalArgumentException {
		setDatum(datum);
	}

	/**
	 * Stelt een nieuwe datum in
	 * 
	 * @param dag
	 *            dag als integer
	 * @param maand
	 *            maand als integer
	 * @param jaar
	 *            jaar als integer
	 * @throws IllegalArgumentException
	 */
	public void setDatum(int dag, int maand, int jaar)
			throws IllegalArgumentException {
		setDag(dag);
		setMaand(Maand.getMaandUitInteger(maand));
		setJaar(jaar);
		if (!isValid())
			throw new IllegalArgumentException("Foutieve datum.");
	}

	/**
	 * Stelt de nieuw waarden in van dag, maand en jaar, met als invoer een
	 * string in formaat DD/MM/JJJJ
	 * 
	 * @param datum
	 * 
	 * @throws IllegalArgumentException
	 */
	public void setDatum(String datum) throws IllegalArgumentException {
		if (datum.length() != 10) throw new IllegalArgumentException("Foutieve datum string.");
		try {
			if(!"/".equals(datum.substring(2, 3)) && !"/".equals(datum.substring(5, 6)))  
				throw new IllegalArgumentException("Scheidingstekens moeten \"/\" zijn");
			String dag = datum.substring(0, 2);
			String maand = datum.substring(3, 5);
			String jaar = datum.substring(6, 10);
			setDatum(Integer.parseInt(dag), Integer.parseInt(maand), Integer.parseInt(jaar));
		}catch (IllegalArgumentException e) {
			throw new IllegalArgumentException(e);
		}catch (Exception e) { //eenderd welke exception -> er kunnen er meerdere zijn
			throw new IllegalArgumentException("Foutieve datum string opgegeven.");
		}
	}

	/**
	 * Stelt een nieuwe dag in.
	 * 
	 * @param dag
	 *            de dag als geheel getal
	 * @throws IllegalArgumentException
	 */
	private void setDag(int dag) throws IllegalArgumentException {
		if (dag < 1 || dag > 31)
			throw new IllegalArgumentException("Foutief dag.");
		this.dag = dag;
	}

	/**
	 * Stelt een nieuwe waarde in voor maand.
	 * 
	 * @param maand
	 *            De maand van de enum Maand
	 * @throws IllegalArgumentException
	 */
	private void setMaand(Maand maand) throws IllegalArgumentException {
		if (maand == null)
			throw new IllegalArgumentException("Maand mag niet null zijn.");
		this.maand = maand;
	}

	/**
	 * Stelt een nieuwe waarde in voor jaar.
	 * 
	 * @param jaar
	 *            Jaartal als geheel getal
	 * @throws IllegalArgumentException
	 */
	private void setJaar(int jaar) throws IllegalArgumentException {
		if (jaar < JaarLimiet.MIN.getJaartal()
				|| jaar > JaarLimiet.MAX.getJaartal())
			throw new IllegalArgumentException("Foutief jaar.");
		this.jaar = jaar;
	}

	/**
	 * Geeft de dag terug als integer
	 * 
	 * @return dag als integer
	 */
	public int getDag() {
		return dag;
	}

	/**
	 * Geeft maand terug als enum Maand
	 * 
	 * @return maand Maand
	 */
	public Maand getMaand() {
		return maand;
	}

	/**
	 * Geeft jaar terug als integer
	 * 
	 * @return jaar als integer
	 */
	public int getJaar() {
		return jaar;
	}

	/**
	 * Geeft de correctheid van de datum terug.
	 * 
	 * @return boolean true als de datum correct is en false als de datum
	 *         foutief is
	 */
	protected boolean isValid() {
		if (dag > maand.getAantalDagen(jaar))
			return false;
		return true;
	}

	/**
	 * Geeft de datum terug in Amerikaans formaat.
	 * 
	 * @return string dat de datum representeert
	 */
	public String getDatumInAmerikaansFormaat() {
		return String.format("%3$d/%2$d/%1$d", dag, maand.getMaandNummer(),
				jaar);
	}

	/**
	 * Geeft de datum terug in Europees formaat.
	 * 
	 * @return string dat de datum representeert
	 */
	public String getDatumInEuropeesFormaat() {
		return String.format("%1$d/%2$d/%3$d", dag, maand.getMaandNummer(),
				jaar);
	}

	@Override
	public String toString() {
		Calendar c = Calendar.getInstance();
		c.set(jaar, maand.getMaandNummer() - 1, dag); //- 1 omdat de maanden blijkbaar bij 0 beginnen
		
		Locale belgium = new Locale("nl","BE");
			
		return String.format(belgium, "%1$te %1$tB %1$tY", c);
	}

	@Override
	public int compareTo(Datum datum) {
		return this.hashCode() - datum.hashCode();
	}

	@Override
	public int hashCode() {
		return jaar * 10000 + maand.getMaandNummer() * 100 + dag;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Datum other = (Datum) obj;
		if (dag != other.dag)
			return false;
		if (jaar != other.jaar)
			return false;
		if (maand == null) {
			if (other.maand != null)
				return false;
		} else if (!maand.equals(other.maand))
			return false;
		return true;
	}

	/**
	 * bepaalt of een datum d kleiner is dan huidig datumobject
	 * 
	 * @param datum
	 *            als Datum object
	 * @return true als meegegeven datum kleiner is dan de datum van huidig
	 *         object, anders false
	 */
	public boolean kleinerDan(Datum datum) {
		if (this.compareTo(datum) < 0)
			return true;
		return false;
	}

	/**
	 * Geeft het verschil terug in aantal jaren tussen huidige datum en datum
	 * meegegeven
	 * 
	 * @param datum
	 *            Datum object
	 * @return aantal jaren verschil
	 */
	public int verschilInJaren(Datum datum) {
		Datum begindatum;
		Datum einddatum;
		// bepaal eerst de begin en einddatum
		if (this.compareTo(datum) < 0) {
			begindatum = this;
			einddatum = datum;
		} else if (this.compareTo(datum) > 0) {
			begindatum = datum;
			einddatum = this;
		} else {
			return 0;
		}
		int verschil = einddatum.getJaar() - begindatum.getJaar() - 1;

		// als beginmaand kleiner is dan eindmaand of als de maanden gelijk zijn
		// en begindag kleiner of gelijk is aan einddag
		if (begindatum.getMaand().getMaandNummer() < einddatum.getMaand()
				.getMaandNummer()
				|| (begindatum.getMaand().getMaandNummer() == einddatum
						.getMaand().getMaandNummer() && begindatum.getDag() <= einddatum
						.getDag())) {
			verschil++;
		}
		return verschil;
	}

	/**
	 * Geeft het verschil terug in aantal maanden tussen huidige datum en datum
	 * meegegeven
	 * 
	 * @param datum
	 *            Datum object
	 * @return aantal maanden verschil
	 */
	public int verschilInMaanden(Datum datum) {
		Datum begindatum;
		Datum einddatum;
		// bepaal eerst de begin en einddatum
		if (this.compareTo(datum) < 0) {
			begindatum = this;
			einddatum = datum;
		} else if (this.compareTo(datum) > 0) {
			begindatum = datum;
			einddatum = this;
		} else {
			return 0;
		}
		int verschil;

		if (begindatum.getJaar() == einddatum.getJaar()) {
			verschil = einddatum.getMaand().getMaandNummer()
					- begindatum.getMaand().getMaandNummer() - 1; // de
																	// volledige
																	// maanden
		} else {
			/**
			 * Gebruikte procedure:
			 * <ul>
			 * <li>bereken de overgebleven volledige maanden in beginjaar</li>
			 * <li>tel het product van de volledige jaren tussen begin en
			 * einddatum met 12 erbij</li>
			 * <li>tel de verlopen maanden van einddatum</li>
			 * <li>als de dag van begindatum kleiner is dan de dag van einddatum
			 * -> tel 1 maand op bij het verschil
			 * </ul>
			 */
			verschil = 12 - begindatum.getMaand().getMaandNummer();
			verschil += 12 * (einddatum.getJaar() - begindatum.getJaar() - 1);
			verschil += einddatum.getMaand().getMaandNummer() - 1;
		}

		if (begindatum.getDag() <= einddatum.getDag() || begindatum.getDag() > einddatum.getMaand().getAantalDagen(einddatum.getJaar()))
			verschil++;

		return verschil;
	}

	/**
	 * Geeft het verschil terug in aantal dagen tussen huidige datum en datum
	 * meegegeven
	 * 
	 * @param datum
	 *            Datum object
	 * @return aantal dagen verschil
	 */
	public int verschilInDagen(Datum datum) {
		Datum begindatum;
		Datum einddatum;
		// bepaal eerst de begin en einddatum
		if (this.compareTo(datum) < 0) {
			begindatum = this;
			einddatum = datum;
		} else if (this.compareTo(datum) > 0) {
			begindatum = datum;
			einddatum = this;
		} else {
			return 0;
		}

		// als het jaar gelijk is van beide datums
		if (begindatum.getJaar() == einddatum.getJaar()) {
			return getAantalDagenVerlopenInJaar(einddatum)
					- getAantalDagenVerlopenInJaar(begindatum);
		}

		/**
		 * als het jaar niet gelijk is dan: Gebruikte procedure:
		 * <ul>
		 * <li>bereken eerst aantal dagen, verlopen in het jaar van de
		 * begindatum</li>
		 * <li>trek dat aantal dagen af van de max dagen mogelijk in dat jaar</li>
		 * <li>tel alle dagen erbij op van alle volledige jaren tussen de 2
		 * datums</li>
		 * <li>tel daarbij de dagen op verlopen in de einddatum</li>
		 * </ul>
		 */
		int verschil = getAantalDagenInJaar(begindatum.getJaar())
				- getAantalDagenVerlopenInJaar(begindatum);
		for (int i = begindatum.getJaar() + 1; i < einddatum.getJaar(); i++) {
			verschil += getAantalDagenInJaar(i);
		}
		verschil += getAantalDagenVerlopenInJaar(einddatum);

		return verschil;
	}

	private static int getAantalDagenVerlopenInJaar(Datum datum) {
		int totaalDagen = 0;

		Maand maand = datum.getMaand();
		int jaar = datum.getJaar();

		for (Maand m : Maand.values()) {
			if (m.getMaandNummer() < maand.getMaandNummer()) {
				totaalDagen += m.getAantalDagen(jaar);
			}
		}
		return totaalDagen + datum.getDag();
	}

	private static int getAantalDagenInJaar(int jaar) {
		if (isSchrikkeljaar(jaar))
			return 366;
		return 365;
	}

	/**
	 * Kijkt na of een jaar een schrikkeljaar is of niet...
	 * 
	 * @param jaar
	 *            integer
	 * @return boolean true als het een schrikkeljaar is, false als dat niet zo
	 *         is
	 */
	public static boolean isSchrikkeljaar(int jaar) {
		if (jaar % 4 == 0 && (jaar % 100 != 0 || jaar % 400 == 0))
			return true;
		return false;
	}

	@Override
	public Object[] getInfoToBeSaved(IPersistentieController pController) {
		return pProvider.getInfoToBeSaved(pController);
		
	}
	public IPersistentieProvider getPersistentieProvider(){
		return pProvider;
	}
	public void setPersistentieProvider(IPersistentieProvider provider){
		this.pProvider = provider;
	}

}